package org.hako.recite.word.tool;

import static org.hako.recite.word.tool.KannjiYomiDetectResult.ERROR_CODE_FAILURE;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import jxl.Sheet;
import jxl.Workbook;
import jxl.read.biff.BiffException;

import org.hako.recite.word.model.Word;

public class NewKannjiYomiAnalyzer {

	private HiraganaTable hiraganaTable;
	private static Map<String, Set<String>> onnKannjiMap;
	private static Map<String, Set<String>> kunnKannjiMap;
	private SingleKannjiDetector singleKannjiDetector;
	private SameCharsKannjiDetector sameCharsKannjiDetector;
	private DoubleKannjiDetector doubleKannjiDetector;
	private DuplicatedKannjiDetector duplicatedKannjiDetector;
	private static Map<String, String> ganaMap;

	/**
	 * Create
	 * 
	 * @throws Exception
	 */
	public NewKannjiYomiAnalyzer() throws Exception {
		super();
		loadKannjiYomi();
		hiraganaTable = new HiraganaTable();
		singleKannjiDetector = new SingleKannjiDetector();
		sameCharsKannjiDetector = new SameCharsKannjiDetector();
		doubleKannjiDetector = new DoubleKannjiDetector();
		duplicatedKannjiDetector = new DuplicatedKannjiDetector();
	}

	private void debugKannjiYomiMap() {
		for (Map.Entry<String, Set<String>> entry : onnKannjiMap.entrySet()) {
			System.out.println(entry.getKey() + "\t"
					+ Arrays.toString(entry.getValue().toArray()));
		}
	}

	/**
	 * Load kannji yomi.
	 * 
	 * @return
	 * @throws IOException
	 * @throws BiffException
	 */
	static void loadKannjiYomi() throws BiffException, IOException {
		onnKannjiMap = new HashMap<String, Set<String>>();
		kunnKannjiMap = new HashMap<String, Set<String>>();
		Workbook workbook = Workbook.getWorkbook(new File(
				"/home/xnnyygn/Programs/recite-word/kannji-yomi.xls"));
		Sheet sheet = workbook.getSheet(0);
		int rowCount = sheet.getRows();
		String lastKannji = null;
		for (int row = 1; row < rowCount; row++) {
			String kannji = sheet.getCell(0, row).getContents();
			if (kannji.length() == 0) {
				kannji = lastKannji;
			}
			String onnyomi = sheet.getCell(1, row).getContents();
			String kunnyomi = sheet.getCell(2, row).getContents();
			if (onnyomi.length() > 0) {
				addToKannjiYomiMap(kannji, convertKatakanaToHiragana(onnyomi),
						onnKannjiMap);
			}
			if (kunnyomi.length() > 0) {
				int indexOfDot = kunnyomi.indexOf("･");
				if (indexOfDot >= 0) {
					kunnyomi = kunnyomi.substring(0, indexOfDot);
				}
				addToKannjiYomiMap(kannji, kunnyomi, kunnKannjiMap);
			}
			lastKannji = kannji;
		}
		workbook.close();
	}

	private static String convertKatakanaToHiragana(String katakana) {
		if (ganaMap == null) {
			ganaMap = new HashMap<String, String>();
			int length = HiraganaTable.HIRAGANA.length();
			for (int i = 0; i < length; i++) {
				ganaMap.put(HiraganaTable.KATAKANA.substring(i, i + 1),
						HiraganaTable.HIRAGANA.substring(i, i + 1));
			}
		}
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < katakana.length(); i++) {
			builder.append(ganaMap.get(katakana.substring(i, i + 1)));
		}
		return builder.toString();
	}

	/**
	 * Add kannji and yomi to map.
	 * 
	 * @param kannji
	 * @param yomi
	 */
	private static void addToKannjiYomiMap(String kannji, String yomi,
			Map<String, Set<String>> kannjiYomiMap) {
		if (kannjiYomiMap.containsKey(kannji)) {
			Set<String> yomiSet = kannjiYomiMap.get(kannji);
			if (!yomiSet.contains(yomi)) {
				yomiSet.add(yomi);
			}
		} else {
			Set<String> yomis = new HashSet<String>();
			yomis.add(yomi);
			kannjiYomiMap.put(kannji, yomis);
		}
	}

	/**
	 * Analyze.
	 * 
	 * @param words
	 * @return
	 */
	public KannjiYomiAnalysisResult analyze(List<Word> words) {
		return analyze(words, 0, 1);
	}

	/**
	 * Analyze.
	 * 
	 * @param words
	 * @param kannjiColumnIndex
	 * @param yomiColumnIndex
	 * @return
	 */
	public KannjiYomiAnalysisResult analyze(List<Word> words,
			int kannjiColumnIndex, int yomiColumnIndex) {
		Map<KannjiYomiPair, List<String>> kannjiWordMap = new TreeMap<KannjiYomiPair, List<String>>(
				new KannjiYomiPairComparator());
		List<Word> firstFailureWords = new ArrayList<Word>();
		List<KannjiExtractResult> extractResults = new ArrayList<KannjiExtractResult>();
		List<Word> secondFailureWords = new ArrayList<Word>();
		for (Word word : words) {
			String rawKannji = word.get(kannjiColumnIndex);
			KannjiExtractResult extractResult = extractKannji(rawKannji);
			if (extractResult.getCount() == 0) {
				continue;
			}
			KannjiYomiDetectResult detectResult = analyzeWithoutExperience(
					rawKannji, word.get(yomiColumnIndex), extractResult);
			if (detectResult.isSuccess()) {
				addPairs(detectResult.getPairs(),
						detectResult.getKannjiWithYomi(), kannjiWordMap);
			} else {
				if (detectResult.isOmitThisWOrd()) {
					secondFailureWords.add(word);
				} else {
					firstFailureWords.add(word);
					extractResults.add(extractResult);
				}
			}
		}
		for (int i = 0; i < firstFailureWords.size(); i++) {
			Word word = firstFailureWords.get(i);
			KannjiYomiDetectResult detectResult = analyzeWithExperience(
					word.get(kannjiColumnIndex), word.get(yomiColumnIndex),
					extractResults.get(i));
			if (detectResult.isSuccess()) {
				addPairs(detectResult.getPairs(),
						detectResult.getKannjiWithYomi(), kannjiWordMap);
			} else {
				secondFailureWords.add(word);
			}
		}
		return new KannjiYomiAnalysisResult(kannjiWordMap, secondFailureWords);
	}

	/**
	 * Extract kannji from rawKannji.
	 * 
	 * @param rawKannji
	 * @return
	 */
	KannjiExtractResult extractKannji(String rawKannji) {
		KannjiExtractResult result = new KannjiExtractResult();
		int length = rawKannji.length();
		for (int i = 0; i < length; i++) {
			if (!hiraganaTable.contains(rawKannji.charAt(i))) {
				result.add(i);
			}
		}
		return result;
	}

	/**
	 * Analyze without experience.
	 * 
	 * @param rawKannji
	 * @param rawYomi
	 * @param extractResult
	 * @return
	 */
	KannjiYomiDetectResult analyzeWithoutExperience(String rawKannji,
			String rawYomi, KannjiExtractResult extractResult) {
		if (singleKannjiDetector.check(rawKannji, rawYomi, extractResult)) {
			KannjiYomiDetectResult result = singleKannjiDetector.detect(
					rawKannji, rawYomi, extractResult);
			checkKannjiYomi(result.getPairs().get(0));
			return result;
		}
		if (duplicatedKannjiDetector.check(rawKannji, rawYomi, extractResult)) {
			KannjiYomiDetectResult result = duplicatedKannjiDetector.detect(
					rawKannji, rawYomi, extractResult);
			if (result.isSuccess()) {
				if (checkKannjiYomi(result.getPairs().get(0))) {
					return result;
				}
			}
		}
		if (sameCharsKannjiDetector.check(rawKannji, rawYomi, extractResult)) {
			KannjiYomiDetectResult result = sameCharsKannjiDetector.detect(
					rawKannji, rawYomi, extractResult);
			for (KannjiYomiPair pair : result.getPairs()) {
				if (!checkKannjiYomi(pair)) {
					new KannjiYomiDetectResult(ERROR_CODE_FAILURE);
				}
			}
			return result;
		}
		if (doubleKannjiDetector.check(rawKannji, rawYomi, extractResult)) {
			KannjiYomiDetectResult result = doubleKannjiDetector.detect(
					rawKannji, rawYomi, extractResult);
			if (result.isSuccess()) {
				if (checkKannjiYomi(result.getPairs().get(0))
						&& checkKannjiYomi(result.getPairs().get(1))) {
					return result;
				}
			}
		}
		return new KannjiYomiDetectResult(ERROR_CODE_FAILURE);
	}

	private boolean checkKannjiYomi(KannjiYomiPair kannjiYomiPair) {
		String kannji = kannjiYomiPair.getKannji();
		String yomi = kannjiYomiPair.getYomi();
		boolean isInYonnKannjis = onnKannjiMap.containsKey(kannji)
				&& onnKannjiMap.get(kannji).contains(yomi);
		if (isInYonnKannjis) {
			kannjiYomiPair.setYonn(true);
			return true;
		}

		boolean isInKunnKannjis = kunnKannjiMap.containsKey(kannji)
				&& kunnKannjiMap.get(kannji).contains(yomi);
		if (isInKunnKannjis) {
			kannjiYomiPair.setYonn(false);
			return true;
		}
		return false;
	}

	/**
	 * Analyze with experience.
	 * 
	 * @param rawKannji
	 * @param rawYomi
	 * @param extractResult
	 * @return
	 */
	KannjiYomiDetectResult analyzeWithExperience(String rawKannji,
			String rawYomi, KannjiExtractResult extractResult) {
		if (extractResult.getCount() == 2) {
			int firstKannjiIndex = extractResult.get(0);
			int secondKannjiIndex = extractResult.get(1);
			String firstKannji = rawKannji.substring(firstKannjiIndex,
					firstKannjiIndex + 1);
			String secondKannji = rawKannji.substring(secondKannjiIndex,
					secondKannjiIndex + 1);
			boolean containsFirstKannji = onnKannjiMap.containsKey(firstKannji);
			boolean containsSecondKannji = onnKannjiMap
					.containsKey(secondKannji);
			if (containsFirstKannji || containsSecondKannji) {
				Set<String> firstKannjiYomis = onnKannjiMap.get(firstKannji);
				if (firstKannjiYomis == null) {
					firstKannjiYomis = new HashSet<String>(0);
				}
				Set<String> secondKannjiYomis = onnKannjiMap.get(secondKannji);
				if (secondKannjiYomis == null) {
					secondKannjiYomis = new HashSet<String>(0);
				}
				int rightLimit = rawYomi.length() - rawKannji.length() + 2;
				for (int i = firstKannjiIndex + 1; i < rightLimit; i++) {
					String leftYomi = rawYomi.substring(firstKannjiIndex, i);
					String rightYomi = rawYomi.substring(i, rightLimit);
					if (firstKannjiYomis.contains(leftYomi)
							|| secondKannjiYomis.contains(rightYomi)) {
						KannjiYomiPair firstPair = new KannjiYomiPair(
								firstKannji, leftYomi);
						KannjiYomiPair secondPair = new KannjiYomiPair(
								secondKannji, rightYomi);
						if (checkKannjiYomi(firstPair)
								&& checkKannjiYomi(secondPair)) {
							return new KannjiYomiDetectResult(Arrays.asList(
									firstPair, secondPair),
									generateKannjiWithYomi(rawKannji,
											firstKannjiIndex,
											secondKannjiIndex, firstPair,
											secondPair));
						}
					}
				}
			}
		}
		return new KannjiYomiDetectResult(ERROR_CODE_FAILURE);
	}

	/**
	 * Add pair.
	 * 
	 * @param pairs
	 * @param kannjiWithYomi
	 */
	private void addPairs(List<KannjiYomiPair> pairs, String kannjiWithYomi,
			Map<KannjiYomiPair, List<String>> kannjiWordMap) {
		for (KannjiYomiPair pair : pairs) {
			addPair(pair, kannjiWithYomi, kannjiWordMap);
		}
	}

	/**
	 * Add pair.
	 * 
	 * @param pair
	 * @param kannjiWithYomi
	 */
	private void addPair(KannjiYomiPair pair, String kannjiWithYomi,
			Map<KannjiYomiPair, List<String>> kannjiWordMap) {

		// add pair to kannji word map
		if (kannjiWordMap.containsKey(pair)) {
			List<String> words = kannjiWordMap.get(pair);
			words.add(kannjiWithYomi);
		} else {
			List<String> words = new ArrayList<String>();
			words.add(kannjiWithYomi);
			kannjiWordMap.put(pair, words);
		}

	}

	private String generateKannjiWithYomi(String rawKannji,
			int firstKannjiIndex, int secondKannjiIndex,
			KannjiYomiPair firstPair, KannjiYomiPair secondPair) {
		StringBuilder kannjiWithYomiBuilder = new StringBuilder();
		kannjiWithYomiBuilder.append(rawKannji.substring(0,
				firstKannjiIndex + 1));
		kannjiWithYomiBuilder.append('(');
		kannjiWithYomiBuilder.append(firstPair.getYomi());
		kannjiWithYomiBuilder.append(')');
		kannjiWithYomiBuilder.append(rawKannji.substring(firstKannjiIndex + 1,
				secondKannjiIndex + 1));
		kannjiWithYomiBuilder.append('(');
		kannjiWithYomiBuilder.append(secondPair.getYomi());
		kannjiWithYomiBuilder.append(')');
		kannjiWithYomiBuilder
				.append(rawKannji.substring(secondKannjiIndex + 1));
		return kannjiWithYomiBuilder.toString();
	}

}
